package com.itcoon.cloud.framework.transform.handler;


import com.itcoon.cloud.framework.constants.CommonCoreConstants;
import com.itcoon.cloud.framework.model.KeyValue;
import com.itcoon.cloud.framework.transform.AbstractTransformHandler;
import com.itcoon.cloud.framework.transform.DefaultMethodAssembler;
import com.itcoon.cloud.framework.transform.InjectHandler;
import com.itcoon.cloud.framework.transform.MethodAssembler;
import com.itcoon.cloud.framework.transform.config.TransformConfigurationProperties;
import com.itcoon.cloud.framework.transform.constants.TransformFeatureConstants;
import com.itcoon.cloud.framework.transform.converter.ConverterFactory;
import com.itcoon.cloud.framework.transform.definition.InjectDefinition;
import com.itcoon.cloud.framework.transform.definition.ModelDefinition;
import com.itcoon.cloud.framework.transform.definition.ModelPropertyDefinition;
import com.itcoon.cloud.framework.transform.enums.TransformFeature;
import com.itcoon.cloud.framework.transform.proxy.IProxy;
import com.itcoon.cloud.framework.transform.utils.TransformServiceUtils;
import com.itcoon.cloud.framework.utils.MapUtils;
import com.itcoon.cloud.framework.utils.MethodUtil;
import com.itcoon.cloud.framework.utils.SpringReflectionUtils;
import com.itcoon.cloud.framework.utils.SpringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author Miaoxia Yu
 * @date 2020-10-28
 */
public class InterfaceTransformHandler<T, R> extends AbstractTransformHandler<T, R> implements InvocationHandler {

    private static final Logger log = LoggerFactory.getLogger(InterfaceTransformHandler.class);
    private static final InjectHandler INJECT_HANDLER = new InjectHandler();
    private volatile static InterfaceTransformHandler INSTANCE = null;

    public static <T, R> InterfaceTransformHandler<T, R> getInstance(TransformConfigurationProperties properties){
        if(INSTANCE == null){
            synchronized (InterfaceTransformHandler.class){
                if(INSTANCE == null){
                    INSTANCE = new InterfaceTransformHandler<>(properties);
                }
            }
        }
        return INSTANCE;
    }

    private Class<R> targetClass;
    private Type[] actualTypes;
    private Map<String, ModelPropertyDefinition> mpdMap;
    private T source;
    private String[] ignoreProperties;
    private List<InjectDefinition> injectDefinitions;
    private TransformFeature[] features;

    private InterfaceTransformHandler(TransformConfigurationProperties properties) {
        super(properties);
    }


    @Override
    protected R doHandle(ModelDefinition md, Class<R> targetClass, Type[] actualTypes, R target, T source, String[] ignoreProperties, List<InjectDefinition> injectDefinitions, TransformFeature... features) {
        this.targetClass = targetClass;
        this.actualTypes = actualTypes;
        this.mpdMap = MapUtils.map(md.getPropertyDefinitions(), ModelPropertyDefinition::getTargetAttribute);
        this.source = source;
        this.ignoreProperties = ignoreProperties;
        this.injectDefinitions = injectDefinitions;
        this.features = features;
        Class<?>[] interfaces = targetClass.getInterfaces();
        Class<?>[] finalInterfaces = new Class[interfaces.length + 2];
        finalInterfaces[0] = targetClass;
        finalInterfaces[1] = IProxy.class;
        for (int i = 0; i < interfaces.length; i++) {
            finalInterfaces[i + 2] = interfaces[i];
        }
        return (R)Proxy.newProxyInstance(targetClass.getClassLoader(), finalInterfaces, this);
    }

    private <T1, R1> R1 doAssemble(T1 source, Class<R1> targetClass, Type[] actualTypes, String[] ignoreProperties, List<InjectDefinition> injectDefinitions, TransformFeature... features){
        MethodAssembler<T1, R1> methodAssembler = new DefaultMethodAssembler<>(SpringUtils.getBean(ConverterFactory.class));
        return methodAssembler.assemble(source, targetClass, actualTypes, ignoreProperties, injectDefinitions, features);
    }

    private Object getOriginPropertyValue(T source, Method originGetter, Field field) throws InvocationTargetException, IllegalAccessException {
        Object methodParameterValue = null;
        if(originGetter != null){
            methodParameterValue = originGetter.invoke(source);
        }
        if(field != null){
            methodParameterValue = SpringReflectionUtils.getField(source, field.getName());
        }
        return methodParameterValue;
    }

    private TransformFeature getTransformWay(TransformFeature... features){
        return Arrays.stream(features).filter(TransformFeatureConstants.TRANSFORM_TYPES::contains).findAny().orElse(TransformFeature.BY_FIELD);
    }

    private List<InjectDefinition> filterInjectDefinition(List<InjectDefinition> injectDefinitions, String targetAttribute){
        List<InjectDefinition> results = new ArrayList<>();
        for (InjectDefinition definition : injectDefinitions) {
            String attribute = definition.getAttribute();
            if(attribute.contains(".") && attribute.split("\\.")[0].equals(targetAttribute)){
                attribute = attribute.substring(attribute.indexOf(".") + 1);
                InjectDefinition newDefinition = new InjectDefinition(attribute, definition.getInjectMode(), definition.getSource());
                results.add(newDefinition);
            }
        }
        return results;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (!method.getName().startsWith(CommonCoreConstants.GETTER_PREFIX)) {
            return method.invoke(this, args);
        }
        String methodAttribute = MethodUtil.getMethodAttribute(method);
        Object sourceValue = null;
        InjectDefinition injectDefinition = INJECT_HANDLER.support(methodAttribute, injectDefinitions);
        ModelPropertyDefinition mpd = mpdMap.get(methodAttribute);
        if(injectDefinition != null){
            return INJECT_HANDLER.handle(targetClass, injectDefinition, source);
        }else{
            if(mpd.getOriginGetter() == null && mpd.getOriginField() == null){
                return method.invoke(proxy, args);
            }
            sourceValue = getOriginPropertyValue(source, mpd.getOriginGetter(), mpd.getOriginField());
            if(sourceValue == null){
                return method.invoke(proxy, args);
            }
            List<String> ignorePropertyList = Arrays.stream(ignoreProperties).filter(s-> s.contains(".") && s.split("\\.")[0].equals(mpd.getTargetAttribute())).map(s->s.substring(s.indexOf(".") + 1)).collect(Collectors.toList());
            List<InjectDefinition> filterInjectDefinitions = filterInjectDefinition(injectDefinitions, mpd.getTargetAttribute());
            Type targetPropertyType = method.getGenericReturnType();
            KeyValue keyValue = TransformServiceUtils.explain(targetPropertyType, targetClass, actualTypes);
            Object originValue = getOriginPropertyValue(source, mpd.getOriginGetter(), mpd.getOriginField());
            Object assembleValue = originValue;
            if(keyValue.getKey() != null){
                assembleValue = doAssemble(originValue, (Class<?>) keyValue.getKey(), (Type[])keyValue.getValue(), ignoreProperties, filterInjectDefinitions, features);
            }

            return assembleValue;
        }
    }
}
